package com.caipos.actor;

import com.caipos.actor.common.actors.KbActorRef;
import com.caipos.actor.common.actors.KbActorSystem;
import com.caipos.actor.service.executors.ExternalCallExecutorService;
import com.caipos.service.*;
import com.caipos.actor.common.data.event.RuleNodeDebugEvent;
import com.caipos.actor.common.data.id.EntityId;
import com.caipos.actor.common.data.id.TenantId;
import com.caipos.actor.common.data.queue.discovery.KbServiceInfoProvider;
import com.caipos.actor.common.msg.KbActorMsg;
import com.caipos.actor.common.msg.KbMsg;
import com.caipos.actor.service.ActorService;
import com.caipos.util.JacksonUtil;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class ActorSystemContext {

    @Getter
    @Setter
    private ActorService actorService;

    @Value("${actors.session.max_concurrent_sessions_per_device:1}")
    @Getter
    private long maxConcurrentSessionsPerDevice;

    @Value("${actors.session.sync.timeout:10000}")
    @Getter
    private long syncSessionTimeout;

    @Value("${actors.rule.chain.error_persist_frequency:3000}")
    @Getter
    private long ruleChainErrorPersistFrequency;

    @Value("${actors.rule.node.error_persist_frequency:3000}")
    @Getter
    private long ruleNodeErrorPersistFrequency;

    @Value("${actors.statistics.enabled:true}")
    @Getter
    private boolean statisticsEnabled;

    @Value("${actors.statistics.persist_frequency:3600000}")
    @Getter
    private long statisticsPersistFrequency;

    @Value("${edges.enabled:true}")
    @Getter
    private boolean edgesEnabled;

    @Value("${cache.type:caffeine}")
    @Getter
    private String cacheType;

    @Getter
    private boolean localCacheType;

    @PostConstruct
    public void init() {
        this.localCacheType = "caffeine".equals(cacheType);
    }

    @Value("${actors.tenant.create_components_on_init:true}")
    @Getter
    private boolean tenantComponentsInitEnabled;

    @Value("${actors.rule.allow_system_mail_service:true}")
    @Getter
    private boolean allowSystemMailService;

    @Value("${actors.rule.allow_system_sms_service:true}")
    @Getter
    private boolean allowSystemSmsService;

    @Value("${transport.sessions.inactivity_timeout:300000}")
    @Getter
    private long sessionInactivityTimeout;

    @Value("${transport.sessions.report_timeout:3000}")
    @Getter
    private long sessionReportTimeout;

    @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
    @Getter
    private boolean debugPerTenantEnabled;

    @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration:50000:3600}")
    @Getter
    private String debugPerTenantLimitsConfiguration;

    @Value("${actors.rpc.submit_strategy:BURST}")
    @Getter
    private String rpcSubmitStrategy;

    @Value("${actors.rpc.close_session_on_rpc_delivery_timeout:false}")
    @Getter
    private boolean closeTransportSessionOnRpcDeliveryTimeout;

    @Value("${actors.rpc.response_timeout_ms:30000}")
    @Getter
    private long rpcResponseTimeout;

    @Value("${actors.rpc.max_retries:5}")
    @Getter
    private int maxRpcRetries;

    @Value("${actors.rule.external.force_ack:false}")
    @Getter
    private boolean externalNodeForceAck;

    @Value("${state.rule.node.deviceState.rateLimit:1:1,30:60,60:3600}")
    @Getter
    private String deviceStateNodeRateLimitConfig;

    @Getter
    @Setter
    private KbActorSystem actorSystem;

    @Setter
    private KbActorRef appActor;

    @Getter
    @Setter
    private KbActorRef statsActor;

    @Autowired
    @Getter
    private TenantService tenantService;

    @Autowired
    @Getter
    @Setter
    private KbServiceInfoProvider serviceInfoProvider;

    @Autowired
    @Getter
    private EventService eventService;

    public ScheduledExecutorService getScheduler() {
        return actorSystem.getScheduler();
    }

    public void schedulePeriodicMsgWithDelay(KbActorRef ctx, KbActorMsg msg, long delayInMs, long periodInMs) {
        log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
        getScheduler().scheduleWithFixedDelay(() -> ctx.tell(msg), delayInMs, periodInMs, TimeUnit.MILLISECONDS);
    }

    public void scheduleMsgWithDelay(KbActorRef ctx, KbActorMsg msg, long delayInMs) {
        log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
        if (delayInMs > 0) {
            getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS);
        } else {
            ctx.tell(msg);
        }
    }

    @Autowired
    @Getter
    private RuleChainService ruleChainService;

    @Autowired
    @Getter
    private RuleNodeService ruleNodeService;

    @Autowired
    @Getter
    private KbTenantProfileCache tenantProfileCache;

    private static final FutureCallback<Void> RULE_NODE_DEBUG_EVENT_ERROR_CALLBACK = new FutureCallback<>() {
        @Override
        public void onSuccess(@Nullable Void event) {

        }

        @Override
        public void onFailure(Throwable th) {
            log.error("Could not save debug Event for Node", th);
        }
    };

    public void persistDebugInput(TenantId tenantId, EntityId entityId, KbMsg tbMsg, String relationType) {
        persistDebugAsync(tenantId, entityId, "IN", tbMsg, relationType, null, null);
    }

    private void persistDebugAsync(TenantId tenantId, EntityId entityId, String type, KbMsg tbMsg, String relationType, Throwable error, String failureMessage) {
        try {
            RuleNodeDebugEvent.RuleNodeDebugEventBuilder event = RuleNodeDebugEvent.builder()
                    .tenantId(tenantId)
                    .entityId(entityId.getId())
                    .serviceId(getServiceId())
                    .eventType(type)
                    .eventEntity(tbMsg.getOriginator())
                    .msgId(tbMsg.getId())
                    .msgType(tbMsg.getType())
                    .dataType(tbMsg.getDataType().name())
                    .relationType(relationType)
                    .data(tbMsg.getData())
                    .metadata(JacksonUtil.toString(tbMsg.getMetaData().getData()));

            if (error != null) {
                event.error(toString(error));
            } else if (failureMessage != null) {
                event.error(failureMessage);
            }

            ListenableFuture<Void> future = eventService.saveAsync(event.build());
            Futures.addCallback(future, RULE_NODE_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
        } catch (IllegalArgumentException ex) {
            log.warn("Failed to persist rule node debug message", ex);
        }
    }

    private String toString(Throwable e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }

    public String getServiceId() {
        return serviceInfoProvider.getServiceId();
    }

    @Autowired
    @Getter
    private ExternalCallExecutorService externalCallExecutorService;

}
